package com.hrpc.loadbalancer.impl;

import com.hrpc.hRpcBootStarp;
import com.hrpc.loadbalancer.AbstractLoadBalancer;
import com.hrpc.loadbalancer.Selector;
import com.hrpc.transport.message.hRpcRequest;

import java.net.InetSocketAddress;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

public class ConsistentHashBalancer extends AbstractLoadBalancer {

    @Override
    protected Selector getSelector(List<InetSocketAddress> serviceList) {
        return new ConsistentSelector(serviceList,128);
    }

    /**
     * 一致性hash的具体算法实现
     */
    private static class ConsistentSelector implements Selector {
        //需要一个hash环，用来存储我们的服务器节点
        private SortedMap<Integer,InetSocketAddress> circle=new TreeMap<>();
        //虚拟节点的个数
        private int virtualNodes;

        public ConsistentSelector(List<InetSocketAddress> serviceList,int virtualNodes) {
            //构造器：尝试将节点转换为虚拟节点进行挂载
            this.virtualNodes=virtualNodes;
            for (InetSocketAddress address : serviceList) {
                //需要将每一个节点加入到hash环中
                addNodeToCircle(address);
            }
        }

        /**
         * 将每个虚拟节点挂载到hash环上
         * @param address
         */
        private void addNodeToCircle(InetSocketAddress address) {
            //为每一个节点生成匹配的虚拟节点进行挂载
            for (int i = 0; i < virtualNodes; i++) {
                int hash=hash(address.toString() + "-" + i);
                //挂载到hash环circle中
                circle.put(hash,address);
            }
        }

        private void removeNodeFromCircle(InetSocketAddress address) {
            //为每一个节点生成匹配的虚拟节点进行挂载
            for (int i = 0; i < virtualNodes; i++) {
                int hash=hash(address.toString() + "-" + i);
                //在环上删除节点
                circle.remove(hash);
            }
        }

        @Override
        public InetSocketAddress getNext() {
            //1.hash环已经建立好了，接下来需要对请求的要素做处理，我们应该选择什么要素来处理hash运算
            //有没有办法可以获取到具体的请求内容，就是hRpcRequest --> threadLocal
            hRpcRequest hRpcRequest = hRpcBootStarp.REQUEST_THREAD_LOCAL.get();
            //2.我们要根据请求的一些特征来选择服务器
            String requestId = Long.toString(hRpcRequest.getRequestId()) ;
            //对请求的ID做hash，字符串默认的hash不太好
            int hash=hash(requestId);
            //判断该hash值是否能直接落在一个服务器上，和服务器的hash一致时直接返回
            if (!circle.containsKey(hash)){
                //寻找离我最近的一个节点，
                SortedMap<Integer, InetSocketAddress> tailMap = circle.tailMap(hash);
                hash = tailMap.isEmpty() ? circle.firstKey() : tailMap.firstKey();
            }
            return circle.get(hash);
        }


        /**
         * 具体的hash算法  TODO 这样还是不均匀的
         * @param s
         * @return
         */
        private int hash(String s) {
            //使用MD5进行计算hash
            MessageDigest md;
            try {
                md = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                throw new RuntimeException(e);
            }
            byte[] bytes = md.digest(s.getBytes());
            //通过MD5得到的结果是一个字节数组，但是我们想要一个int
            int res=0;
            for (int i = 0; i < 4; i++) {
                res = res<<8;
                if (bytes[i] < 0){
                    res = res | (bytes[i] & 255);
                }else {
                    res=res | bytes[i];
                }
            }
            return res;
        }
        private String toBinary(int i){
            String s = Integer.toBinaryString(i);
            int length = 32-s.length();
            StringBuilder sb=new StringBuilder();
            for (int j=0;j<32;j++){
                sb.append(0);
            }
            sb.append(s);
            return sb.toString();
        }
    }
}
